# InheritanceConfigurationResolver.java

    package org.codefilarete.stalactite.engine.configurer.dslresolver;

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import javax.annotation.Nullable;

    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.MethodReferences;
    import org.codefilarete.reflection.SerializablePropertyMutator;
    import org.codefilarete.stalactite.dsl.MappingConfigurationException;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.entity.FluentEntityMappingBuilder;
    import org.codefilarete.stalactite.dsl.idpolicy.IdentifierPolicy;
    import org.codefilarete.stalactite.dsl.key.FluentEntityMappingBuilderKeyOptions;
    import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ElementCollectionTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.MapEntryTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.TableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.model.IdentifierMapping;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.VisibleForTesting;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.KeepOrderSet;
    import org.codefilarete.tool.function.SerializableTriFunction;

    import static org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.KeyMapping;
    import static org.codefilarete.tool.Nullable.nullable;

    /**
     * A class that collects the configurations inherited by a {@link EntityMappingConfiguration} inheritance to build
     * a {@link ResolvedConfiguration} for each configuration in the hierarchy.
     * The {@link ResolvedConfiguration}s are some ready-to-use instances that contain all the necessary information to build
     * an entity, without the need to go through the whole hierarchy again.
     *
     * @param <C> the initial configuration entity type
     * @param <I> the configurations identifier type
     * @author Guillaume Mary
     */
    public class InheritanceConfigurationResolver<C, I> {

    	@VisibleForTesting
    	static final SerializableTriFunction<FluentEntityMappingBuilder, SerializablePropertyMutator<?, ?>, IdentifierPolicy, FluentEntityMappingBuilderKeyOptions<?, ?>> IDENTIFIER_METHOD_REFERENCE = FluentEntityMappingBuilder::mapKey;

    	/**
    	 * Returns a {@link ResolvedConfiguration} for each configuration in the given configuration hierarchy
    	 *
    	 * @param mappingConfiguration the configuration to start from
    	 * @return a {@link ResolvedConfiguration} for each configuration in the hierarchy

    	 */
    	KeepOrderSet<ResolvedConfiguration<?, I>> resolveConfigurations(EntityMappingConfiguration<C, I> mappingConfiguration) {
    		NamingConfiguration defaultNamingConfiguration = new NamingConfiguration(
    				TableNamingStrategy.DEFAULT,
    				ColumnNamingStrategy.DEFAULT,
    				ForeignKeyNamingStrategy.DEFAULT,
    				UniqueConstraintNamingStrategy.DEFAULT,
    				ElementCollectionTableNamingStrategy.DEFAULT,
    				MapEntryTableNamingStrategy.DEFAULT,
    				JoinColumnNamingStrategy.JOIN_DEFAULT,
    				ColumnNamingStrategy.INDEX_DEFAULT,
    				AssociationTableNamingStrategy.DEFAULT);

    		NamingConfiguration inheritedNaming = defaultNamingConfiguration;
    		KeyMapping<?, I> inheritedKey;
    		EntityMappingConfiguration<?, ?> keyDefiner = null;

    		List<EntityMappingConfiguration<?, I>> bottomToTopConfigurations = Iterables.asList(mappingConfiguration.inheritanceIterable());
    		Iterable<EntityMappingConfiguration<?, I>> topToBottomConfigurations = () -> Iterables.reverseIterator(bottomToTopConfigurations);

    		// Using the same top-to-bottom loop to determine the naming strategies and the identifier because those
    		// data follow the same and usual inheritance principle: the ancestor applies if none is defined locally
    		KeepOrderSet<ResolvedConfiguration<?, I>> topToBottomResult = new KeepOrderSet<>();
    		for (EntityMappingConfiguration<?, I> node : topToBottomConfigurations) {
    			ResolvedConfiguration<?, I> nodeConfiguration = new ResolvedConfiguration<>(node);
    			topToBottomResult.add(nodeConfiguration);

    			// 1. Computing naming conventions: it follows usual inheritance rules, the highest configuration declares default
    			// behaviors, which can be overridden by sub-configurations
    			NamingConfiguration nodeNamingConventions = new NamingConfiguration(
    					nullable(node.getTableNamingStrategy()).getOr(inheritedNaming.getTableNamingStrategy()),
    					nullable(node.getColumnNamingStrategy()).getOr(inheritedNaming.getColumnNamingStrategy()),
    					nullable(node.getForeignKeyNamingStrategy()).getOr(inheritedNaming.getForeignKeyNamingStrategy()),
    					nullable(node.getUniqueConstraintNamingStrategy()).getOr(inheritedNaming.getUniqueConstraintNamingStrategy()),
    					nullable(node.getElementCollectionTableNamingStrategy()).getOr(inheritedNaming.getElementCollectionTableNamingStrategy()),
    					nullable(node.getEntryMapTableNamingStrategy()).getOr(inheritedNaming.getEntryMapTableNamingStrategy()),
    					nullable(node.getJoinColumnNamingStrategy()).getOr(inheritedNaming.getJoinColumnNamingStrategy()),
    					nullable(node.getIndexColumnNamingStrategy()).getOr(inheritedNaming.getIndexColumnNamingStrategy()),
    					nullable(node.getAssociationTableNamingStrategy()).getOr(inheritedNaming.getAssociationTableNamingStrategy()));

    			nodeConfiguration.setNamingConfiguration(nodeNamingConventions);
    			inheritedNaming = nodeNamingConventions;

    			// 2. Key mapping: it follows the inheritance rules:
    			// - null for ancestors before definer,
    			// - null for descendants
    			// - thus, can't be overridden by sub-configurations: an exception is thrown where it's redefined
    			if (node.getKeyMapping() != null) {
    				if (keyDefiner != null) {
    					// only one definer is allowed in the full chain
    					throw new MappingConfigurationException("Identifier policy is defined twice in the hierarchy : first by "
    							+ AccessorDefinition.toString(keyDefiner.getKeyMapping().getAccessor())
    							+ ", then by " + AccessorDefinition.toString(node.getKeyMapping().getAccessor()));
    				}
    				inheritedKey = node.getKeyMapping();
    				keyDefiner = node;
    				nodeConfiguration.setKeyMapping(inheritedKey);
    			}
    		}

    		if (keyDefiner == null) {
    			throw newMissingIdentificationException(mappingConfiguration.getEntityType());
    		}

    		Map<EntityMappingConfiguration<?, I>, ResolvedConfiguration<?, I>> configurationPerMapping = Iterables.map(topToBottomResult, ResolvedConfiguration::getMappingConfiguration, () -> new HashMap<>());
    		// 3. deducing Tables from configuration hierarchy
    		// - it's the opposite of usual inheritance rules: the lowest configuration defines the table, which is shared with ancestors if joining tables is not defined
    		// - when joining tables flag is set, a new table is created for parent configuration
    		// Note that this step depend on naming strategy for Table creation, hence it must be done after naming strategy resolution
    		Table currentSegmentTable = takeConfigurationTableOrCreateOne(mappingConfiguration, configurationPerMapping);
    		EntityMappingConfiguration<?, I> pawn = mappingConfiguration;
    		while(pawn.getInheritanceConfiguration() != null) {
    			configurationPerMapping.get(pawn).setTable(currentSegmentTable);
    			if (pawn.getInheritanceConfiguration().isJoiningTables()) {
    				EntityMappingConfiguration<?, I> parentMappingConfiguration = pawn.getInheritanceConfiguration().getParentMappingConfiguration();
    				Table joiningTable = takeConfigurationTableOrCreateOne(parentMappingConfiguration, configurationPerMapping);
    				currentSegmentTable = joiningTable;
    			}
    			pawn = pawn.getInheritanceConfiguration().getParentMappingConfiguration();
    		}
    		// handling the latest configuration because algorithm above didn't iterate over it
    		configurationPerMapping.get(pawn).setTable(currentSegmentTable);

    		List<ResolvedConfiguration<?, I>> result = new ArrayList<>(topToBottomResult.getDelegate());
    		Collections.reverse(result);
    		return new KeepOrderSet<>(result);
    	}

    	protected UnsupportedOperationException newMissingIdentificationException(Class<C> entityType) {
    		return new UnsupportedOperationException("Identifier is not defined for " + Reflections.toString(entityType)
    				+ ", please add one through " + MethodReferences.toMethodReferenceString(IDENTIFIER_METHOD_REFERENCE) + " variants");
    	}

    	@Nullable
    	private <X, Y> Table takeConfigurationTableOrCreateOne(EntityMappingConfiguration<X, Y> mappingConfiguration, Map<EntityMappingConfiguration<?, Y>, ResolvedConfiguration<?, Y>> result) {
    		return nullable(mappingConfiguration.getTable())
    				.elseSet(() -> new Table(result.get(mappingConfiguration).getNamingConfiguration()
    						.getTableNamingStrategy()
    						.giveName(mappingConfiguration.getEntityType())))
    				.get();
    	}

    	public static class ResolvedConfiguration<C, I> {

    		private final EntityMappingConfiguration<C, I> mappingConfiguration;

    		private Table table;

    		private NamingConfiguration namingConfiguration;

    		/**
    		 * Null for ancestors before definer, inherited for descendants
    		 */
    		@Nullable
    		private KeyMapping<C, I> keyMapping;

    		@Nullable
    		private IdentifierMapping<C, I> identifierMapping;

    		ResolvedConfiguration(EntityMappingConfiguration<C, I> node) {
    			this.mappingConfiguration = node;
    		}

    		public EntityMappingConfiguration<C, I> getMappingConfiguration() {
    			return mappingConfiguration;
    		}

    		public Table getTable() {
    			return table;
    		}

    		public void setTable(Table table) {
    			this.table = table;
    		}

    		public NamingConfiguration getNamingConfiguration() {
    			return namingConfiguration;
    		}

    		public void setNamingConfiguration(NamingConfiguration namingConfiguration) {
    			this.namingConfiguration = namingConfiguration;
    		}

    		@Nullable
    		public KeyMapping getKeyMapping() {
    			return keyMapping;
    		}

    		public void setKeyMapping(@Nullable KeyMapping keyMapping) {
    			this.keyMapping = keyMapping;
    		}

    		@Nullable
    		public IdentifierMapping getIdentifierMapping() {
    			return identifierMapping;
    		}

    		public void setIdentifierMapping(@Nullable IdentifierMapping identifierMapping) {
    			this.identifierMapping = identifierMapping;
    		}
    	}
    }